using System;
using System.Diagnostics;
using System.Text;
using GameStateManagement.GravityWorld;
using GameStateManagement.GravityWorld.Input;
using GameStateManagement.Input;
using GameStateManagement.Model.Entities.MapParsing;
using GameStateManagement.View.Gui;
using GameStateManagement.View.Gui.Components;
using GameStateManagement.View.Rendering;
using GameStateManagement.View.ScreenManager;
using GameStateManagement.View.Sprites;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using GameStateManagement.Model;
using System.IO;
using System.Xml.Serialization;
using System.Xml;

namespace GameStateManagement.View.Screens
{
    /// <summary>
    /// This screen implements the actual game logic. It is just a
    /// placeholder to get the idea across: you'll probably want to
    /// put some more interesting gameplay in here!
    /// </summary>
    public class GameplayScreen : GameScreen
    {
        #region Fields

        private SpriteManager _spriteManager;
        private GuiManager _guiManager;
        private TextField _gameMessage;
        private GravityModel _activeWorld;
        private RenderManager _renderManager;
        private InputFsm _controller;
        private Camera _camera;

        #endregion

        #region Properties

        public SpriteManager SpriteManager
        {
            get { return _spriteManager; }
            protected set { _spriteManager = value; }
        }

        public GuiManager GuiManager
        {
            get { return _guiManager; }
            protected set { _guiManager = value; }
        }

        #endregion

        #region Events

        public void NewGameMessage(object sender, EventArgs e)
        {
            var game = sender as GameModel;
            if (_gameMessage != null && game != null)
                _gameMessage.Text = game.GameMessage;
        }

        private void RestartButtonClicked(object sender, EventArgs e)
        {
            _activeWorld.RestartGame();
        }

        #endregion

        #region Initialization

        public GameplayScreen()
        {
            IsSerializable = true;
            BlockDrawUnder = true;
            BlockUpdatesUnder = true;
            EnabledGestures =
                GestureType.Tap |
                GestureType.FreeDrag |
                GestureType.DragComplete;
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);

        }

        public override void LoadContent()
        {
            InitializeTextureManagers();
            InitializeActiveWorld();
            InitializeGui();
            InitializeCamera();
            InitializeControler();

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime();

            _activeWorld.StartGame();
        }

        private void InitializeCamera()
        {
            _camera = new Camera(ScreenManager.SpriteBatch);
        }

        private void InitializeTextureManagers()
        {
            _spriteManager = new SpriteManager(ScreenManager.Game);
            _renderManager = new RenderManager(ScreenManager.SpriteBatch, _spriteManager);
        }

        private void InitializeControler()
        {
            _controller = new InputFsm();

            _controller.AddControler(new CameraTouchInputHandler(_controller, _camera), ControlerPriority.Low);
            _controller.AddControler(new TrajectoryInputHandler(_controller, _activeWorld), ControlerPriority.Medium);
            _controller.AddControler(new GuiTouchControler(_controller, _activeWorld, _guiManager), ControlerPriority.High);
        }

        private void InitializeGui()
        {
            _guiManager = new GuiManager(ScreenManager.SpriteBatch);

            _gameMessage = new TextField(new Vector2(0, ScreenManager.Width + 10), _spriteManager.LoadSpriteFont("gamefont"));
            _guiManager.AddComponent(_gameMessage);

            var restartButton = new Button(new Vector2(ScreenManager.Width - 80, 0), 80, 80,
                                         SpriteManager.LoadSprite("startover"), SpriteManager.LoadSprite("startover"));

            restartButton.Clicked += RestartButtonClicked;

            _guiManager.AddComponent(restartButton);
        }

        private void InitializeActiveWorld()
        {
            if (_activeWorld == null)
            {
                _activeWorld = new GravityModel(_renderManager);
            }

            _activeWorld.HostingScreen = this;

            _activeWorld.ChangeMap(MapLoader.GenerateMap(""));

            _activeWorld.InitializeModel(false);

            _activeWorld.NewMessage += NewGameMessage;

            _renderManager.LoadContent(this, _activeWorld.TextureNameList);

        }

        public override void UnloadContent()
        {
            _spriteManager.ClearSprites();
        }

        #endregion

        #region Update and Draw


        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

            if (IsActive)
            {
                _activeWorld.Update(gameTime);
                _guiManager.Update(gameTime);
            }
        }

        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState inputState)
        {

            if (inputState == null)
                throw new ArgumentNullException("inputState");

            _controller.HandleInput(inputState);

            PlayerIndex player;
            if (inputState.IsNewButtonPress(Buttons.Back, ControllingPlayer, out player))
            {
                LoadingScreen.Load(ScreenManager, false, ControllingPlayer, new BackgroundScreen(), new MainMenuScreen());
            }

        }

        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
                                               Color.CornflowerBlue, 0, 0);

            _camera.BeginFrame();

            _renderManager.RenderGameModel(_activeWorld);
            _guiManager.Draw(gameTime);

            _camera.EndFrame();

            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0)
                ScreenManager.FadeBackBufferToBlack(1f - TransitionAlpha);
        }

        #endregion

        #region Serialization

        public override void Serialize(Stream stream)
        {
            using (XmlWriter writer = XmlWriter.Create(stream))
            {
                var sb = new StringBuilder();
                XmlWriter wriiter = XmlWriter.Create(sb);
                var serializer = new XmlSerializer(_activeWorld.GetType());
                serializer.Serialize(wriiter, _activeWorld);
                serializer.Serialize(writer, _activeWorld);
                Debug.WriteLine(sb.ToString());
            }
        }

        public override void Deserialize(Stream stream)
        {
            using (XmlReader reader = XmlReader.Create(stream))
            {
                var serializer = new XmlSerializer(typeof(GravityModel));
                _activeWorld = serializer.Deserialize(reader) as GravityModel;
            }
        }

        #endregion

    }
}
